﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;
using System.Globalization;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace Engine.Dtd {
    internal sealed class UrlResolver {
        private static Regex contentTypeRegex = new Regex(@"((?'type'[!#-'*-+\-.0-9A-Z^-z|~]+)\s*\/\s*(?'subtype'[!#-'*-+\-.0-9A-Z^-z|~]+))+(?'parameter'\s*;\s*(?'attribute'[!#-'*-+\-.0-9A-Z^-z|~]+)\s*=\s*(?'value'[!#-'*-+\-.0-9A-Z^-z|~]+))?", RegexOptions.ExplicitCapture | RegexOptions.CultureInvariant);

        public UrlResolver() {
            Credentials = CredentialCache.DefaultCredentials;
            DefaultEncoding = Encoding.UTF8;
            }

        public ICredentials Credentials { get; set; }
        public Uri ResolvedUrl { get; private set; }
        public Encoding DefaultEncoding { get; set; }

        public static Uri ResolveUrl(Uri baseUrl, string relativeUrl) {
            Uri address = new Uri(relativeUrl, UriKind.RelativeOrAbsolute);
            if (address.IsAbsoluteUri)
                return address;
            if (baseUrl == null)
                throw new InvalidOperationException("Could not resolve URL because specified address is relative and base URL is null.");
            return new Uri(baseUrl, address);
            }

        public TextReader GetEntity(Uri absoluteUri) {
            Guard.ArgumentNotNull(absoluteUri, "absoluteUri");
            if (!absoluteUri.IsAbsoluteUri)
                throw new ArgumentException("The uniform resource identifier is not absolute.");
            Debug.Assert(DefaultEncoding != null, "The DefaultEncoding is null.");
            switch (absoluteUri.Scheme) {
                case "file":
                    ResolvedUrl = absoluteUri;
                    return new StreamReader(new FileStream(absoluteUri.LocalPath, FileMode.Open, FileAccess.Read), DefaultEncoding);
                case "https":
                case "http":
                    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(absoluteUri);
                    webRequest.UserAgent = "Mozilla/4.0 (compatible;);";
                    webRequest.Timeout = 180000;
                    webRequest.Proxy = null;
                    // Pass the cookie associated with request.
                    webRequest.CookieContainer = null;
                    webRequest.PreAuthenticate = false;
                    // Pass the credentials of the process. 
                    webRequest.Credentials = Credentials;

                    WebResponse webResponse = webRequest.GetResponse();
                    ResolvedUrl = webResponse.ResponseUri;
                    Encoding encoding = ParseCharSet(webResponse.ContentType);
                    if (encoding == null)
                        encoding = DefaultEncoding;
                    return new StreamReader(webResponse.GetResponseStream(), encoding);
                default:
                    throw new NotSupportedException(string.Format("The scheme '{0}' is not supported.", absoluteUri.Scheme));
                }
            }

        public static Encoding ParseCharSet(string contentType) {
            Guard.ArgumentNotNull(contentType, "contentType");
            Match match = contentTypeRegex.Match(contentType);
            if (match.Success && match.Groups["parameter"].Success && string.Equals(match.Groups["attribute"].Value, "charset", StringComparison.OrdinalIgnoreCase)) {
                try {
                    return Encoding.GetEncoding(match.Groups["value"].Value);
                    }
                catch (ArgumentException) {
                    // Ignore this exception
                    // Name is not a valid code page name or
                    // The code page indicated by name is
                    // not supported by the underlying platform.
                    }
                }
            return null;
            }
        }
    }
